﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;

namespace HealthMonitor.Extensions
{
    /// <summary>
    /// http://visualstudiomagazine.com/Articles/2011/04/01/pccsp_Expression-Trees.aspx
    /// </summary>
    public static class ExpressionExtensions
    {
        public static Action<T> ToValueAssignExpression<TInstance, T>(this TInstance source, Expression<Func<TInstance, T>> getter)
        {
            Guard.ThrowIfNull(() => getter);

            if (getter.Parameters.Count != 1 || !(getter.Body is MemberExpression))
                throw new ArgumentException(
                        @"Input expression must be a single parameter field getter, e.g. g => g._fieldToSet  or function(g) g._fieldToSet");
            var parms = new[] { getter.Parameters[0], Expression.Parameter(typeof(T), "value") };
            Expression body = Expression.Call(AssignmentHelper<T>.MethodInfoSetValue,
                    new[] { getter.Body, parms[1] });
            var action = Expression.Lambda<Action<TInstance, T>>(body, parms).Compile();
            Action<T> resultAction = v => action(source, v);
            return resultAction;
        }
        public static void SetIf<T, TValue>(this T destination, Expression<Func<T, TValue>> destinationExpression, TValue value, Func<TValue, bool> condition)
        {
            if (condition(value))
                destinationExpression.ToFieldAssignExpression().Compile()(destination, value);
        }
        public static Expression<Action<TInstance, TProp>> ToFieldAssignExpression<TInstance, TProp>(this Expression<Func<TInstance, TProp>> fieldGetter)
        {
            Guard.ThrowIfNull(() => fieldGetter);

            if (fieldGetter.Parameters.Count != 1 || !(fieldGetter.Body is MemberExpression))
                throw new ArgumentException(
                        @"Input expression must be a single parameter field getter, e.g. g => g._fieldToSet  or function(g) g._fieldToSet");
            var parms = new[] { fieldGetter.Parameters[0], Expression.Parameter(typeof(TProp), "value") };
            Expression body = Expression.Call(AssignmentHelper<TProp>.MethodInfoSetValue,
                    new[] { fieldGetter.Body, parms[1] });

            return Expression.Lambda<Action<TInstance, TProp>>(body, parms);
        }
        #region Nested type: AssignmentHelper

        private class AssignmentHelper<T>
        {
            internal static readonly MethodInfo MethodInfoSetValue =
            typeof(AssignmentHelper<T>).GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Static);

            // ReSharper disable UnusedMember.Local
            // Used via reflection
            private static void SetValue(ref T target, T value)
            // ReSharper restore UnusedMember.Local
            {
                target = value;
            }
        }

        #endregion
        /// <summary>
        /// Marc Gravell
        /// http://stackoverflow.com/questions/491429/how-to-get-the-propertyinfo-of-a-specific-property/491486#491486
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        internal static Expression ReduceLambda(this Expression expression)
        {
            Expression body = expression;
            if (body is LambdaExpression)
                return ((LambdaExpression)(body)).Body;
            return body;
        }

        public static MemberExpression AsMemberOrThrow(this Expression expression)
        {
            return expression.ReduceLambda().AsOrThrow<MemberExpression>(); //String.Format(StaticReflection.ErrorFormat, "member"),"expression");
        }

        public static MemberExpression AsMemberOrThrow<T>(this Expression<T> expression)
        {
            return expression.ReduceLambda().AsOrThrow<MemberExpression>(); //String.Format(StaticReflection.ErrorFormat, "member"),"expression");
        }


        
    }
}